
/**
 * @author stefan.liebenberg@lfpug.com (Stefan)
 */

goog.provide('lfpug.ui.Renderer');

goog.require('goog.dom');
goog.require('goog.dom.classes');
goog.require('goog.functions');
goog.require('goog.string');
goog.require('lfpug.ui.ComponentFactory');
goog.require('lfpug.ui.type.Model');
goog.require('lfpug.ui.type.Template');
goog.require('lfpug.ui.type.TemplateObject');
goog.require('templates.lfpug.ui.Component');



/**
 * This class is a a singleton class designed to provide functionality
 * like the @code {goog.ui.ContainerRenderer}, but for normal components
 * extended from @code {lfpug.ui.Component}.
 *
 * @constructor
 */
lfpug.ui.Renderer = function() {};
goog.addSingletonGetter(lfpug.ui.Renderer);


/**
 * Custom CSS Class.
 * @const
 * @type {string}
 */
lfpug.ui.Renderer.CSS_CLASS =
    goog.getCssName('ui-component');


/**
 * @return {string}
 *         The custom css class.
 */
lfpug.ui.Renderer.prototype.getCssClass =
    goog.functions.constant(lfpug.ui.Renderer.CSS_CLASS);


/**
 * @const
 * @type {lfpug.ui.type.Template}
 */
lfpug.ui.Renderer.TEMPLATE =
    /** @type {lfpug.ui.type.Template} */
    (templates.lfpug.ui.Component.Structure);


/**
 * @return {lfpug.ui.type.Template}
 *         The template Function.
 */
lfpug.ui.Renderer.prototype.getTemplate =
    goog.functions.constant(lfpug.ui.Renderer.TEMPLATE);


/**
 * @param {lfpug.ui.Component} component
 *        Component object to render.
 *
 * @return {lfpug.ui.type.TemplateObject}
 *         Returns the template object.
 */
lfpug.ui.Renderer.prototype.getTemplateObject =
    function(component) {
  var object = {};
  object.Id = component.getId();
  object.CssClass = this.getCssClass();
  object.TagName = this.getTagName();
  return object;
};


/**
 *
 * @param {lfpug.ui.Component} component
 *        Component object to render.
 * @return {string} The result html.
 */
lfpug.ui.Renderer.prototype.getTemplateContent =
    function(component)  {
  var template, object;
  template = this.getTemplate();
  object = this.getTemplateObject(component);
  return template(object);
};


/**
 * The tag name for elements in this renderer.
 *
 * @const
 * @type {string}
 */
lfpug.ui.Renderer.TAG_NAME = 'DIV';


/**
 * @return {string}
 *         Returns the tag name.
 */
lfpug.ui.Renderer.prototype.getTagName =
    goog.functions.constant(lfpug.ui.Renderer.TAG_NAME);


/**
 * @param {lfpug.ui.Component} component
 *        Component object to render.
 *
 * @return {Element}
 *         The root element for this component.
 */
lfpug.ui.Renderer.prototype.createDom =
    function(component) {
  var domHelper, html;
  domHelper = component.getDomHelper();
  html = this.getTemplateContent(component);
  return /** @type {!Element} */ (domHelper.htmlToDocumentFragment(html));
};


/**
 * @param {Element} element
 *        Root element of the component.
 * @return {Element}
 *         Content element of the component.
 */
lfpug.ui.Renderer.prototype.getContentElement =
    goog.functions.identity;


/**
 * @param {Element} element
 *        Root element to this component.
 * @return {boolean}
 *        True if element can be decorated.
 */
lfpug.ui.Renderer.prototype.canDecorate = function(element) {
  return element.tagName === this.getTagName();
};


/**
 * Decorates element for component.
 *
 * @param {lfpug.ui.Component} component
 *        Component to decorate onto element.
 *
 * @param {Element} element
 *        Element to decorate component onto.
 *
 * @return {Element}
 *         Root element of component.
 */
lfpug.ui.Renderer.prototype.decorate =
    function(component, element) {
  if (element.id) {
    component.setId(element.id);
  }
  goog.dom.classes.add(element, this.getCssClass());
  this.decorateChildren(component, this.getContentElement(element));
  return element;
};


/**
 * Decorates the children within a component.
 *
 * @param {lfpug.ui.Component} component
 *        The Component to which element belongs.
 *
 * @param {Element} element
 *        The element to search for children.
 *
 * @param {Element=} opt_firstChild
 *        The first child to be decorated.
 *
 * @suppress {visibility} setElementInternal
 */
lfpug.ui.Renderer.prototype.decorateChildren =
    function(component, element, opt_firstChild) {
  if (element) {

    var node = opt_firstChild || element.firstChild, next;
    while (node && node.parentNode == element) {

      // Get the next sibling here, since the node may be replaced or removed.
      next = node.nextSibling;

      if (node.nodeType == goog.dom.NodeType.ELEMENT) {

        // Decorate element node.
        var child = this.getDecoratorForChild(/** @type {Element} */(node));

        if (child) {
          // addChild() may need to look at the element.
          child.setElementInternal(/** @type {Element} */(node));
          component.addChild(child);
          child.decorate(/** @type {Element} */(node));
        }
      } else if (!node.nodeValue || goog.string.trim(node.nodeValue) == '') {
        // Remove empty text node, otherwise madness ensues (e.g. controls that
        // use goog-inline-block will flicker and shift on hover on Gecko).
        element.removeChild(node);
      }
      node = next;
    }
  }
};


/**
 * Fetches a decorator for this child.
 *
 * @param {Element} child_element
 *        The child element to decorate.
 *
 * @return {goog.ui.Component?}
 *         Returns a component that can decorate the given element.
 */
lfpug.ui.Renderer.prototype.getDecoratorForChild =
    function(child_element) {

  return /** @type {goog.ui.Component} */(
      goog.ui.registry.getDecorator(child_element));

};


/**
 * @const
 * @type {lfpug.ui.ComponentFactory.FactoryContext}
 */
lfpug.ui.Renderer.FACTORY_CONTEXT =
    lfpug.ui.ComponentFactory.getUniqueContext('ComponentRenderer');


/**
 * @return {lfpug.ui.ComponentFactory.FactoryContext}
 *         The factory context used.
 */
lfpug.ui.Renderer.prototype.getFactoryContext =
    goog.functions.constant(lfpug.ui.Renderer.FACTORY_CONTEXT);



